A2A 的訊息裡有三個 ID,長得像但職責完全不同。這是我在讀 log 時卡最久的地方,所以單獨寫一篇。
| 欄位 | 生命週期 | 作用 |
|---|---|---|
messageId |
每則訊息換新 | 單則訊息的識別 |
taskId |
每次委託換新 | 一次工作的識別,狀態機掛在它身上 |
contextId |
整段對話不變 | 讓 remote agent 認出「同一使用者的同一段對話」 |
答案是 contextId。
remote agent 收到請求後,用 contextId 去自己的 task store 撈同一 context 的歷史訊息。所以使用者說「我要兩個」的時候,對面那隻 agent 知道前文是哪一份菜單。
容易搞錯的是拿 taskId 當記憶鍵。它每次委託都是新的,你用它去撈歷史只會撈到這一次,多輪根本串不起來。這個錯誤的症狀很典型:單輪問答完全正常,一到第二輪就失憶。
orchestrator 把自己的 session id 直接當 contextId 傳下去。
這個做法的好處是兩邊的對話邊界自動對齊,不需要維護額外的映射表。使用者在 orchestrator 這邊開一段新對話,remote agent 那邊也自然是一段新 context。
在 log 裡的表現是:你會看到兩個 UUID 其實是同一個。第一次讀 log 時我以為是巧合或是複製貼上錯誤,後來才發現這是設計。
把 A2A 拿掉,這個問題還在:只要一段對話會跨越多個獨立行程,就得決定誰持有對話身分、由誰傳遞。
A2A 的答案是由 client 產生並持續攜帶,server 端只負責用它查歷史。
這個選擇把狀態一致性的責任放在發起方。好處是 server 端可以完全無狀態地水平擴展,壞處是 client 一旦弄丟 id,server 端那段歷史就變成孤島:資料還在,但沒有人找得到它。
所以如果你的 orchestrator 是重啟就掉 session 的那種,那你在 remote agent 那邊會持續累積永遠不會再被讀到的歷史記錄。這件事不會報錯,只會慢慢變成儲存成本。
遇到「第二輪失憶」時,照這個順序看:
contextId
contextId 是不是同一個(不同就是 client 端每次都在開新 session)三步都是量測,不要先去調 prompt。